#include "libcomm.h"
#ifndef COMM_MSVC 
#define _fseeki64	fseeko
#define _ftelli64	ftello
#define _access		access
File::WriteUnMasker::WriteUnMasker(){ m_mask = umask(0);}
File::WriteUnMasker::~WriteUnMasker(){ umask(m_mask);}
#endif

bool File::MakeDir(const char* dir, int mode)
{
	if(!Access(dir,F_OK))
	{
#ifndef COMM_MSVC
		WriteUnMasker masker; 
		if(0 != mkdir(dir,mode))
		{
			LOGERR("mkdir \"%s\" error: (%d) %s", dir, errno,strerror(errno));
			return false;
		}
#else
		mode = 0;
		if(0 != _mkdir(dir))
		{
			LOGERR("mkdir \"%s\" error: (%d) %s", dir, errno,strerror(errno));
			return false;
		}
#endif
	}
	return true;
}

bool File::ChangeMode(const char* src, int mode)
{
#ifndef COMM_MSVC
	if(0 != chmod(src,mode))
	{
		LOGERR("chmod \"%s\" to mode(%04o) error: (%d) %s", src, mode,errno,strerror(errno));
		return false;
	}
#endif
	return true;
}

bool File::Access(const char* src, int mode)
{
	return (0==_access(src,mode));
}

bool File::Copy(const char* src, const char* dest, size_t block_size)
{
	Buffer block(block_size);
	File f1(src,"rb");
	File f2(dest,"wb");
	if(!f1.IsOpened() || !f2.IsOpened())
		return false;
	do 
	{
		if(0 == f1.Read(block))
			break;
		if(!f2.Write(block))
			return false;
	} while (true);
	return true;
}

bool File::Move(const char* src, const char* dest)
{
	if(!Copy(src,dest))
		return false;
	return Remove(src);
}

bool File::Remove(const char* src)
{
#ifndef COMM_MSVC
	if(0 != unlink(src))
	{
		LOGFUNCERROR(unlink);
		return false;
	}
#else
	if(!DeleteFileA(src))
	{
		LOGFUNCERROR(DeleteFile);
		return false;
	}
#endif
	return true;
}

Buffer File::LoadContent(const char* filename)
{
	Buffer content;
	File f;
	if(!f.Open(filename,"rb") || !f.Load(content))
		content.resize(0);
	return content;
}

String File::LoadContentText(const char* filename)
{
	String content;
	File f;
	if(!f.Open(filename,"r") || !f.LoadText(content))
		content = String::Empty;
	return content;
}

bool File::SaveContent(const char* filename, const void* buffer, size_t size)
{
	File f;
	return (f.Open(filename,"wb") && f.Write(buffer,size));
}


File::File():m_stream(NULL)
{
}

File::File(const char* filename, const char* mode):m_stream(NULL)
{
	this->Open(filename,mode);
}

File::~File()
{
	this->Close();
}

bool File::Open(const char* filename, const char* mode)
{
	ThreadLockGuard guard(&m_lock);
	if(!this->Close())
		return false;
	m_filename = filename;
	MYLIB_ASSERT(NULL==m_stream);
	m_stream = fopen(m_filename,mode);
	if(NULL == m_stream)
	{
		LOGERR("open file \"%s\" in mode \"%s\" error: (%d) %s", this->Name(), mode, errno,strerror(errno));
		return false;
	}
	
	return true;
}

bool File::Close()
{
	ThreadLockGuard guard(&m_lock);
	if(NULL != m_stream)
	{
		if(0 != fclose(m_stream))
		{
			LOGERR("close file \"%s\" error: (%d) %s", this->Name(), errno,strerror(errno));
			return false;
		}
		m_stream = NULL;
	}
	return true;
}

FILE* File::Stream()
{
	ThreadLockGuard guard(&m_lock);
	return m_stream;
}

const char* File::Name()
{
	ThreadLockGuard guard(&m_lock);
	return (char*)m_filename;
}

bool File::IsOpened()
{
	ThreadLockGuard guard(&m_lock);
	return (NULL != m_stream);
}

long long File::Size()
{
	ThreadLockGuard guard(&m_lock);
	MYLIB_ASSERT(IsOpened());
	MYLIB_ASSERT(this->MoveToEnd());
	return this->Position();
}

long long File::Position()
{
	ThreadLockGuard guard(&m_lock);
	MYLIB_ASSERT(IsOpened());
	long long pos = _ftelli64(m_stream);
	MYLIB_ASSERT(-1 != pos);
	return pos;
}

bool File::MoveTo(long long position)
{
	ThreadLockGuard guard(&m_lock);
	MYLIB_ASSERT(IsOpened());
	if(0 != _fseeki64(m_stream,position,SEEK_SET))
	{
		LOGERR("seeking file \"%s\" error: (%d) %s", this->Name(),errno,strerror(errno));
		return false;
	}
	return true;
}

bool File::MoveToBegin()
{
	ThreadLockGuard guard(&m_lock);
	MYLIB_ASSERT(IsOpened());
	if(0 != _fseeki64(m_stream,0,SEEK_SET))
	{
		LOGERR("seeking file \"%s\" error: (%d) %s", this->Name(),errno,strerror(errno));
		return false;
	}
	//rewind(m_stream);
	return true;
}

bool File::MoveToEnd()
{
	ThreadLockGuard guard(&m_lock);
	MYLIB_ASSERT(IsOpened());
	if(0 != _fseeki64(m_stream,0,SEEK_END))
	{
		LOGERR("seeking file \"%s\" error: (%d) %s", this->Name(),errno,strerror(errno));
		return false;
	}
	return true;
}


size_t File::Read(void* buffer, size_t size)
{
	ThreadLockGuard guard(&m_lock);
	size_t bytes = 0;
	if(IsOpened() && !feof(m_stream))
	{
		bytes = fread(buffer,sizeof(char),size,m_stream);
		if(ferror(m_stream))
		{
			LOGERR("reading file \"%s\" error: (%d) %s", this->Name(),errno,strerror(errno));
			bytes = 0;
		}
	}
	return bytes;
}

size_t File::Read(Buffer& buffer)
{
	size_t bytes = this->Read(buffer.get(),buffer.size());
	buffer.resize(bytes);
	return bytes;
}

File& File::operator >>(Buffer& buffer)
{
	this->Read(buffer);
	return *this;
}

bool File::Write(const void* buffer, size_t size)
{
	ThreadLockGuard guard(&m_lock);
	if (!IsOpened())
		return false;
	if(0 != buffer && size > 0 && 1 != fwrite(buffer,size,1,m_stream))
	{
		LOGERR("writing file \"%s\" error: (%d) %s", this->Name(),errno,strerror(errno));
		return false;
	}
	return true;
}

bool File::Write(const Buffer& buffer)
{
	return this->Write(buffer.get(),buffer.size());
}

bool File::WriteText(const char* format, ...)
{
	ThreadLockGuard guard(&m_lock);
	if (!IsOpened())
		return false;
	int err;
	va_list args;
	va_start(args, format);
	err = vfprintf(m_stream,format,args);
	va_end(args);
	if(-1 == err)
	{
		LOGFUNCERROR(vfprintf);
		return false;
	}
	return true;
}

File& File::operator <<(const Buffer& buffer)
{
	this->Write(buffer);
	return *this;
}

bool File::Flush()
{
	ThreadLockGuard guard(&m_lock);
	if (!IsOpened())
		return false;
	if(0 != fflush(m_stream))
	{
		LOGERR("flushing file \"%s\" error: (%d) %s", this->Name(),errno,strerror(errno));
		return false;
	}
	return true;
}

bool File::Load(Buffer& content)
{
	return LoadToMemory(content,false);
}

bool File::LoadText(Buffer& content)
{
	return LoadToMemory(content,true);
}

bool File::LoadToMemory(Buffer& content, bool asText)
{
	ThreadLockGuard guard(&m_lock);
	if (!IsOpened())
		return false;
	size_t count,total;
	count = total = 0;
	long long filesize = this->Size();
	MYLIB_ASSERT(filesize<0xFFFFFFFF);
	size_t buffersize = filesize & 0xFFFFFFFF;
	content.resize(buffersize + 1); // add ended '\0' for text.
	if(!this->MoveToBegin())
		return false;
	while(!feof(m_stream))
	{
		count = fread(content.get()+total, sizeof(char), content.size(), m_stream);
		if(ferror(m_stream))
		{
			LOGERR("reading file \"%s\" error: (%d) %s", this->Name(),errno,strerror(errno));
			return false;
		}
		total += count;
	}
	MYLIB_ASSERT(total <= buffersize); // may be less in text mode as "\r\n" would convert to "\n" in windows.
	content.resize(asText?(total+1):total); // not need ended '\0'.
	return true;
}


